/* ArrayType.js
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

$package("dowry.datatype");

/**
 * Object representation of an array datatype.
 *
 */
$class("ArrayType").$extends("Datatype").$as(
{
    /**
     * Property indicating the datatype of each array
     * element.  The default is 'string'.
     *
     */
    elementType : "string",

    /**
     * Property indicating the datatype for each array
     * element.
     *
     */
    _elemDatatype : null,

    /**
     * Compares two values for order, based on the
     * comparison rules for this datatype. Returns a
     * negative integer, zero, or a positive integer
     * indicating that the first value is less than, equal
     * to, or greater than the second.
     *
     * <p>
     * In ArrayType, this comparison is based on the
     * compare of the length of each array.
     * </p>
     *
     * @param value1  the first value to be compared
     *
     * @param value2  the second value to be compared
     *
     * @return        a negative integer, zero, or a
     *                positive integer indicating that
     *                the first value is less than, equal
     *                to, or greater than the second.
     *
     */
    compare : function(value1, value2)
    {
        arguments[0] = value1.length;
        arguments[1] = value2.length;

        return this.$super(arguments);
    },

    /**
     * Get the datatype mapped to the elements of the array.
     *
     * @return  the datatype object for the array's elements
     *
     */
    getElementDatatype : function()
    {
        var dt = this._elemDatatype;
        if (dt == null)
        {
            if (this.elementType != null &&
                Dowry.datatypes != null &&
                Dowry.datatypes[this.elementType] != null)
            {
                var datatypeClass = Dowry.datatypes[this.elementType];
                datatypeClass = $classLoader.loadClass(datatypeClass);

                // construct our array element datatype instance...
                var dt = new datatypeClass;

                // TODO: how do we configure this guy?
                // Dowry.configure(dt, cfg);

                this._elemDatatype = dt;
            }
        }

        return dt;
    },

    /**
     * Extends Datatype's <code>toConfiguration()</code> to
     * include the properties of the element's datatype.
     *
     * @param  id    the identifier for the field associated
     *               with this datatype (optional)
     *
     * @param  val   the field value associated with this
     *               datatype (optional)
     *
     * @return       an object containing the datatype
     *               information as a map
     *
     */
    toConfiguration : function(id, val)
    {
        var cfg = null;

        // TODO: how do we handle array's configuration
        var dt = this.getElementDatatype();
        if (dt != null)
        {
            cfg = dt.toConfiguration(id, val);
        }
        else
        {
            cfg = this.$super(arguments);
        }

        return cfg;
    },

    /**
     * Converts some raw value to a value of the appropriate
     * datatype.
     *
     * @param val  the original, raw value representation
     *
     * @return     the value converted to the appropriate
     *             datatype
     *
     */
    toDatatype : function(val)
    {
        // if they haven't passed us an array,
        // assume its an array of 1 item
        var origArr = val;
        if (val == null ||
            val.join == null)
        {
            origArr = [ val ];
        }

        var arr = null;
        var dt = this.getElementDatatype();
        for (var i=0; i < origArr.length; i++)
        {
            var arrVal = origArr[i];

            // convert using the element's datatype
            if (dt != null)
            {
                arrVal = dt.toDatatype(arrVal);
            }

            // no datatype for the key, just use
            // super.toDatatype()
            else
            {
                arguments[0] = arrVal;
                arrVal = this.$super(arguments);
            }

            // set value into array
            if (arrVal != null)
            {
                // don't populate array until we find
                // our first value
                if (arr == null)
                {
                    arr = new Array();
                }

                arr[arr.length] = arrVal;
            }
        }

        return arr;
    },

    /**
     * Converts some value of the appropriate datatype to
     * a string representation.
     *
     * @param val  the original value of the appropriate
     *             datatype
     *
     * @return     the value converted to a string
     *
     */
    toFormattedString : function(val)
    {
        // if they haven't passed us an array,
        // assume its an array of 1 item
        var origArr = val;
        if (val == null ||
            val.join == null)
        {
            origArr = [ val ];
        }

        var arr = null;
        var dt = this.getElementDatatype();
        for (var i=0; i < origArr.length; i++)
        {
            var s = null;
            var arrVal = origArr[i];

            // convert using the element's datatype
            if (dt != null)
            {
                s = dt.toFormattedString(arrVal);
            }

            // no datatype for the key, just use
            // super.toFormattedString() for the provided
            // object's key field
            else
            {
                arguments[0] = arrVal;
                s = this.$super(arguments);
            }

            // set string into array
            if (s != null)
            {
                // don't populate array until we find
                // our first value
                if (arr == null)
                {
                    arr = new Array();
                }

                arr[arr.length] = s;
            }
        }

        return arr;
    },

    /**
     * Validates that:
     *
     * <ol>
     *  <li>  the value is provided, if required
     *  </li>
     *  <li>  the value is compatible with the datatype of
     *        the array's elements
     *  </li>
     * </ol>
     *
     * @param val    a value representation to be validated
     *
     * @return       the string error message, if the
     *               input is invalid; otherwise, null
     *
     */
    validate : function(val)
    {
        var msg = this.$super(arguments);

        var dt = this.getElementDatatype();
        if (msg == null &&
            dt != null)
        {
            // if they haven't passed us an array,
            // assume its an array of 1 item
            var arr = val;
            if (val == null ||
                val.join == null)
            {
                arr = [ val ];
            }

            // loop through until validation produces
            // an error message... or we run out of
            // array elements
            for (var i=0; i < arr.length; i++)
            {
                msg = dt.validate(arr[i]);
                if (msg != null)
                {
                    break;
                }
            }
        }

        return msg;
    }
});